package com.dkts.oil.test;

import com.dkts.oil.mysql.entity.AisPoint;
import com.dkts.oil.postgis.ShipTrackSegmentInsert;

import java.io.IOException;
import java.util.*;

/**
 * 抽稀去噪处理：分开处理分段轨迹的去噪（removeNoise）与抽稀（Douglas-Peucker）
 */
public class GeoJsonExporterV2抽稀去噪处理 {

    // 噪声阈值：速度小于 0.5 节 或 转向速率大于 45 视为噪声
    private static final double SPEED_THRESHOLD = 0.1;
    //转向速率阈值 在去噪时，代码里用这个阈值判断：如果某个轨迹点的转向速率（rot）的绝对值大于45，就认为这个点是噪声，会被过滤掉。
    //简单来说，这个阈值是去噪判定的一个门槛，转向太快（超过45度/秒）会被认为是异常点，可能是测量误差或干扰。
    private static final double TURN_RATE_THRESHOLD = 45.0;

    // Douglas-Peucker 抽稀阈值（经纬度单位）
    private static final double DP_EPSILON = 0.001; // ≈100m

    public static void main(String[] args) throws Exception {


        removeNoiseForData(ParentTest.inputCsvPath,  ParentTest.outputDir+"_抽稀去噪",UUID.randomUUID().toString());
    }

    private  static Map<String, List<List<AisPoint>>> removeNoiseForData(String csvPath,String outPath,String uuid) throws Exception {
        // 原始数据读取和分段（外部依赖）
        Map<String, List<AisPoint>> aisPointsByMmsi = GeoJsonExporterV2去重和时间排序.getAisPointsByMmsi(csvPath, outPath,  uuid);
        Map<String, List<List<AisPoint>>> segmented = GeoJsonExporterV2分段处理.segmentAisPoints(aisPointsByMmsi,uuid);

        // 第一步：去噪
        Map<String, List<List<AisPoint>>> denoised = removeNoiseForAll(segmented);

        // 第二步：抽稀
        Map<String, List<List<AisPoint>>> simplified = simplifyForAll(denoised);

        // 输出信息：每个 MMSI 的轨迹段数和每段点数
        for (String mmsi : segmented.keySet()) {
            System.out.printf("=== MMSI=%s ===%n", mmsi);
            List<List<AisPoint>> rawSegs = segmented.get(mmsi);
            List<List<AisPoint>> denoiseSegs = denoised.get(mmsi);
            List<List<AisPoint>> simpSegs = simplified.get(mmsi);
            for (int i = 0; i < rawSegs.size(); i++) {
                int rawSize = rawSegs.get(i).size();
                int denSize = denoiseSegs.get(i).size();
                int simpSize = simpSegs.get(i).size();
                System.out.printf("段 %d: 原始=%d, 去噪=%d, 抽稀=%d,相差=%d,最终点数量=%d%n", i + 1, rawSize, denSize, simpSize,  denSize - simpSize,  simpSize);
            }
            System.out.println();
        }
        //simplified 输出geojson，输出到output_geojson_v2_抽稀去噪处理，按照mmsi创建文件夹。文件里里面就是geosjon文件
        ParentTest.exportGeoJson(simplified,  outPath);

        //保存数据库
        ShipTrackSegmentInsert.addSegments(simplified,  uuid);
        return segmented;
    }

    /**
     * 批量对所有轨迹段做去噪处理：如果去噪后为空，则保留原始段
     */
    public static Map<String, List<List<AisPoint>>> removeNoiseForAll(Map<String, List<List<AisPoint>>> segmented) {
        Map<String, List<List<AisPoint>>> result = new HashMap<>();
        for (Map.Entry<String, List<List<AisPoint>>> entry : segmented.entrySet()) {
            List<List<AisPoint>> cleanedSegments = new ArrayList<>();
            for (List<AisPoint> segment : entry.getValue()) {
                List<AisPoint> cleaned = removeNoise(segment);
                if (cleaned.isEmpty()) {
                    cleanedSegments.add(segment); // 保留原始段
                } else {
                    cleanedSegments.add(cleaned);
                }
            }
            result.put(entry.getKey(), cleanedSegments);
        }
        return result;
    }

    /**
     * 批量对所有轨迹段做抽稀处理：如果抽稀后为空，则保留原始段
     */
    public static Map<String, List<List<AisPoint>>> simplifyForAll(Map<String, List<List<AisPoint>>> denoised) {
        Map<String, List<List<AisPoint>>> result = new HashMap<>();
        for (Map.Entry<String, List<List<AisPoint>>> entry : denoised.entrySet()) {
            List<List<AisPoint>> simplifiedSegments = new ArrayList<>();
            for (List<AisPoint> segment : entry.getValue()) {
                List<AisPoint> simplified = douglasPeucker(segment, DP_EPSILON);
                if (simplified.isEmpty()) {
                    simplifiedSegments.add(segment); // 保留原始段
                } else {
                    simplifiedSegments.add(simplified);
                }
            }
            result.put(entry.getKey(), simplifiedSegments);
        }
        return result;
    }


    /**
     * 单段轨迹去噪
     */
    private static List<AisPoint> removeNoise(List<AisPoint> pts) {
        List<AisPoint> filtered = new ArrayList<>();
        for (AisPoint p : pts) {
            if (p.getSpeed() != null && p.getSpeed() < SPEED_THRESHOLD) continue;
            if (p.getRot() != null && Math.abs(p.getRot()) > TURN_RATE_THRESHOLD) continue;
            filtered.add(p);
        }
        // 只有当去噪后点数明显减少时才返回去噪后的点，否则返回原始点
        if (filtered.size() < pts.size() && !filtered.isEmpty()) {
            return filtered;
        } else {
            return pts;
        }
    }


    /**
     * 单段轨迹抽稀（Douglas-Peucker 算法）
     */
    private static List<AisPoint> douglasPeucker(List<AisPoint> pts, double eps) {
        if (pts.size() < 3) return new ArrayList<>(pts);

        boolean[] keep = new boolean[pts.size()];
        keep[0] = true;
        keep[pts.size() - 1] = true;
        dpRecursion(pts, 0, pts.size() - 1, eps, keep);

        List<AisPoint> simplified = new ArrayList<>();
        for (int i = 0; i < pts.size(); i++) {
            if (keep[i]) simplified.add(pts.get(i));
        }

        // 如果抽稀后点数少于原始点数，返回抽稀结果，否则返回原始点列表
        if (simplified.size() < pts.size() && !simplified.isEmpty()) {
            return simplified;
        } else {
            return pts;
        }
    }


    /**
     * 递归实现 Douglas-Peucker
     */
    private static void dpRecursion(List<AisPoint> pts, int i, int j, double eps, boolean[] keep) {
        double maxDist = 0;
        int idx = -1;
        AisPoint a = pts.get(i), b = pts.get(j);
        for (int k = i + 1; k < j; k++) {
            double d = perpendicularDistance(pts.get(k), a, b);
            if (d > maxDist) {
                maxDist = d;
                idx = k;
            }
        }
        if (maxDist > eps && idx != -1) {
            keep[idx] = true;
            dpRecursion(pts, i, idx, eps, keep);
            dpRecursion(pts, idx, j, eps, keep);
        }
    }

    /**
     * 点到线的垂直距离
     */
    private static double perpendicularDistance(AisPoint p, AisPoint a, AisPoint b) {
        double x0 = p.getLon(), y0 = p.getLat();
        double x1 = a.getLon(), y1 = a.getLat();
        double x2 = b.getLon(), y2 = b.getLat();
        double num = Math.abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1);
        double den = Math.hypot(y2 - y1, x2 - x1);
        return den == 0 ? 0 : num / den;
    }
}
